////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   objmancullqueue.cpp
//  Version:     v1.00
//  Created:     2/12/2009 by Michael Glueck
//  Compilers:   Visual Studio.NET
//  Description: Implementation for asynchronous obj-culling queue
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "ObjManCullQueue.h"
#include "CZBufferCuller.h"
#include <IJobManager_JobDelegator.h>

#if defined(USE_CULL_QUEUE)
# define CULL_QUEUE_USE_JOB 1
#else 
# define CULL_QUEUE_USE JOB 0
#endif

#if CULL_QUEUE_USE_JOB
DECLARE_JOB("IsBoxOccluded", TBoxOccludedJob, NCullQueue::SCullQueue::ProcessInternal);
JobManager::SJobState g_OcclJobState;
#endif 




























NCullQueue::SCullQueue::SCullQueue()
: cullBufWriteHeadIndex(0) 
, cullBufReadHeadIndex(0)
, m_TestItemQueueReady(false)
, m_BufferAndCameraReady(false)
, m_MainFrameID(0)
, m_pCullBuffer(NULL)
, m_pCam(NULL)
{



	memset(cullItemBuf,0,sizeof(cullItemBuf));
}

void NCullQueue::SCullQueue::FinishedFillingTestItemQueue()
{
#ifdef USE_CULL_QUEUE
	assert( gEnv->IsEditor() || m_TestItemQueueReady == false );
	m_TestItemQueueReady = true;
	if( m_BufferAndCameraReady == true )
	{
		//If both sets of data are ready, begin the test.
		Process();
	}
#endif
}

void NCullQueue::SCullQueue::SetTestParams(uint32 mainFrameID, const CCullBuffer* pCullBuffer,const CCamera* pCam)
{
	m_MainFrameID = mainFrameID;
	m_pCullBuffer = (CZBufferCuller*)pCullBuffer;
	m_pCam = pCam;
	assert( gEnv->IsEditor() || m_BufferAndCameraReady == false );
	m_BufferAndCameraReady = true;
	if( m_TestItemQueueReady == true )
	{
		//If both sets of data are ready, begin the test.
		Process();
	}
}

void NCullQueue::SCullQueue::Process()
{
#ifdef USE_CULL_QUEUE
	FUNCTION_PROFILER_3DENGINE;
	
	TBoxOccludedJob job(m_MainFrameID, (CZBufferCuller*)m_pCullBuffer, m_pCam);
	job.SetClassInstance(this);
	job.RegisterJobState(&g_OcclJobState);
	job.SetCacheMode(JobManager::SPUBackend::eCM_4);
	job.Run();	
#endif //USE_CULL_QUEUE
}

NCullQueue::SCullQueue::~SCullQueue()
{
#ifdef USE_CULL_QUEUE







#endif
}

void NCullQueue::SCullQueue::Wait()
{
	FUNCTION_PROFILER_3DENGINE;

#ifdef USE_CULL_QUEUE
	gEnv->GetJobManager()->WaitForJob(g_OcclJobState, 300);

	cullBufWriteHeadIndex = 0;
	cullBufReadHeadIndex = 0;
#endif
}

#if CULL_QUEUE_USE_JOB
 SPU_ENTRY(IsBoxOccluded)
#endif



void NCullQueue::SCullQueue::ProcessInternal( uint32 mainFrameID, CZBufferCuller *const pCullBuffer,const CCamera* const pCam )

{
#ifdef USE_CULL_QUEUE














	CZBufferCuller& cullBuffer = *(CZBufferCuller*)pCullBuffer;
	#define localQueue (*this)
	#define localCam (*pCam)




	cullBuffer.BeginFrame(localCam);

	bool feedbackExec = false;//becomes true after one possible feedback loop

//FeedbackLoop:

	{
		SPU_DOMAIN_LOCAL const SCullItem*const cEnd = &localQueue.cullItemBuf[localQueue.cullBufWriteHeadIndex];
		for(uint16 a=1,b=0;b<4;a<<=1,b++)//traverse through all 4 occlusion buffers
		{
			cullBuffer.ReloadBuffer(b);
			for(SCullItem* it = &localQueue.cullItemBuf[ localQueue.cullBufReadHeadIndex ]; it != cEnd; ++it)
			{
				SCullItem& rItem = *it;
				IF(!(rItem.BufferID&a),1)
					continue;

				IF((rItem.BufferID&1),0)	//zbuffer
				{
					if(!cullBuffer.IsObjectVisible(rItem.objBox, eoot_OBJECT, 0.f, &rItem.pOcclTestVars->nLastOccludedMainFrameID))
						rItem.pOcclTestVars->nLastOccludedMainFrameID = mainFrameID;
					else
						rItem.pOcclTestVars->nLastVisibleMainFrameID = mainFrameID;
				}
				else	//shadow buffer
				if(rItem.pOcclTestVars->nLastNoShadowCastMainFrameID != mainFrameID)
				{
					if(cullBuffer.IsObjectVisible(rItem.objBox, eoot_OBJECT, 0.f, &rItem.pOcclTestVars->nLastOccludedMainFrameID))
						rItem.pOcclTestVars->nLastShadowCastMainFrameID = mainFrameID;
				}
			}
		}

		localQueue.cullBufReadHeadIndex = (cEnd - localQueue.cullItemBuf);
		
		//if not visible, set occluded
		for(SCullItem* it = localQueue.cullItemBuf; it != cEnd; ++it)
		{
			SCullItem& rItem = *it;
			IF((rItem.BufferID&6) & (rItem.pOcclTestVars->nLastNoShadowCastMainFrameID != mainFrameID),1)
					rItem.pOcclTestVars->nLastShadowCastMainFrameID = mainFrameID;
		}

	}
/*
	if(false)//!feedbackExec)
	{
#ifdef __SPU__
		//check if somebody has added another item meanwhile or we need to process more items than fit to spu
		//Note that on SPU the localqueue is a separate object. On non-PS3 platforms localqueue is (*this) so this check is tautological
		if(cullBufWriteHeadIndex != localQueue.cullBufWriteHeadIndex)
		{
			//do 1 feedback loop, some items have been added in the meantime, latency high enough to assume all has been stored and not just the curIndex
			memtransfer_from_main(localQueue.cullItemBuf,	&this->cullItemBuf[localQueue.cullBufWriteHeadIndex],(cullBufWriteHeadIndex-localQueue.cullBufWriteHeadIndex)*sizeof(SCullItem), 0);
			localQueue.cullBufWriteHeadIndex = (cullBufWriteHeadIndex-localQueue.cullBufWriteHeadIndex);//to process remaining ones
			localQueue.cullBufReadHeadIndex = 0;	//Start processing from the beginning again
			memtransfer_sync(0);
			feedbackExec = true;
			goto FeedbackLoop;
		}
#else
		//On non-PS3 platforms localQueue is (*this) and there's no chunked processing by copying this object to a SPU
		if( localQueue.cullBufReadHeadIndex < localQueue.cullBufWriteHeadIndex )
		{
			//More stuff was added during processing. Continue where we left off.
			goto FeedbackLoop;
		}
#endif
	}
*/
#endif
	#undef localQueue
	#undef localCam
}
